//	VirtualDub - Video processing and capture application
//	A/V interface library
//	Copyright (C) 1998-2006 Avery Lee
//
//	This program is free software; you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation; either version 2 of the License, or
//	(at your option) any later version.
//
//	This program is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with this program; if not, write to the Free Software
//	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

#include <vd2/system/vdtypes.h>
#include <vd2/Riza/opengl.h>
#include <windows.h>

namespace {
	static const char *const kWGLFunctions[]={
		"wglCreateContext",
		"wglDeleteContext",
		"wglMakeCurrent",
		"wglGetProcAddress",
		"wglSwapBuffers",
	};

	static const char *const kGLFunctions[]={
		"glAlphaFunc",
		"glBegin",
		"glBindTexture",
		"glBlendFunc",
		"glCallList",
		"glClear",
		"glClearColor",
		"glColor4d",
		"glColor4f",
		"glColorMask",
		"glCopyTexSubImage2D",
		"glDeleteLists",
		"glDeleteTextures",
		"glDepthFunc",
		"glDepthMask",
		"glDisable",
		"glDrawBuffer",
		"glEnable",
		"glEnd",
		"glEndList",
		"glFeedbackBuffer",
		"glFinish",
		"glFlush",
		"glGetError",
		"glGetFloatv",
		"glGetIntegerv",
		"glGetTexLevelParameteriv",
		"glGenLists",
		"glGetString",
		"glGenTextures",
		"glLoadIdentity",
		"glLoadMatrixd",
		"glMatrixMode",
		"glNewList",
		"glOrtho",
		"glPixelStorei",
		"glPopAttrib",
		"glPushAttrib",
		"glReadBuffer",
		"glReadPixels",
		"glRenderMode",
		"glTexCoord2d",
		"glTexCoord2f",
		"glTexEnvi",
		"glTexImage1D",
		"glTexImage2D",
		"glTexParameterfv",
		"glTexParameteri",
		"glTexSubImage2D",
		"glVertex2d",
		"glVertex2f",
		"glVertex2i",
		"glViewport",
	};

	static const char *const kGLExtFunctions[]={
		// ARB_multitexture
		"glActiveTextureARB",
		"glMultiTexCoord2fARB",

		// ARB_vertex_buffer_object (EXT_pixel_buffer_object/ARB_pixel_buffer_object)
		"glBindBufferARB",
		"glDeleteBuffersARB",
		"glGenBuffersARB",
		"glIsBufferARB",
		"glBufferDataARB",
		"glBufferSubDataARB",
		"glGetBufferSubDataARB",
		"glMapBufferARB",
		"glUnmapBufferARB",
		"glGetBufferParameterivARB",
		"glGetBufferPointervARB",

		// NV_register_combiners
		"glCombinerParameterfvNV",
		"glCombinerParameterivNV",
		"glCombinerParameterfNV",
		"glCombinerParameteriNV",
		"glCombinerInputNV",
		"glCombinerOutputNV",
		"glFinalCombinerInputNV",
		"glGetCombinerInputParameterfvNV",
		"glGetCombinerInputParameterivNV",
		"glGetCombinerOutputParameterfvNV",
		"glGetCombinerOutputParameterivNV",
		"glGetFinalCombinerInputParameterfvNV",
		"glGetFinalCombinerInputParameterivNV",

		// NV_register_combiners2
		"glCombinerStageParameterfvNV",

		// ATI_fragment_shader
		"glGenFragmentShadersATI",
		"glBindFragmentShaderATI",
		"glDeleteFragmentShaderATI",
		"glBeginFragmentShaderATI",
		"glEndFragmentShaderATI",
		"glPassTexCoordATI",
		"glSampleMapATI",
		"glColorFragmentOp1ATI",
		"glColorFragmentOp2ATI",
		"glColorFragmentOp3ATI",
		"glAlphaFragmentOp1ATI",
		"glAlphaFragmentOp2ATI",
		"glAlphaFragmentOp3ATI",
		"glSetFragmentShaderConstantATI",

		// NV_occlusion_query
		"glGenOcclusionQueriesNV",
		"glDeleteOcclusionQueriesNV",
		"glIsOcclusionQueryNV",
		"glBeginOcclusionQueryNV",
		"glEndOcclusionQueryNV",
		"glGetOcclusionQueryivNV",
		"glGetOcclusionQueryuivNV",

		// EXT_framebuffer_object
		"glIsRenderbufferEXT",
		"glBindRenderbufferEXT",
		"glDeleteRenderbuffersEXT",
		"glGenRenderbuffersEXT",
		"glRenderbufferStorageEXT",
		"glGetRenderbufferParameterivEXT",
		"glIsFramebufferEXT",
		"glBindFramebufferEXT",
		"glDeleteFramebuffersEXT",
		"glGenFramebuffersEXT",
		"glCheckFramebufferStatusEXT",
		"glFramebufferTexture1DEXT",
		"glFramebufferTexture2DEXT",
		"glFramebufferTexture3DEXT",
		"glFramebufferRenderbufferEXT",
		"glGetFramebufferAttachmentParameterivEXT",
		"glGenerateMipmapEXT",

		// WGL_ARB_extensions_string
		"wglGetExtensionsStringARB",

		// WGL_EXT_extensions_string
		"wglGetExtensionsStringEXT",

		// WGL_ARB_make_current_read
		"wglMakeContextCurrentARB",
		"wglGetCurrentReadDCARB",

		// WGL_EXT_swap_control
		"wglSwapIntervalEXT",
		"wglGetSwapIntervalEXT",
	};
}

VDOpenGLBinding::VDOpenGLBinding()
	: mhmodOGL(NULL)
	, mhdc(NULL)
	, mhglrc(NULL)
{
}

VDOpenGLBinding::~VDOpenGLBinding() {
}

bool VDOpenGLBinding::Init() {
	mhmodOGL = LoadLibrary("opengl32");
	if (!mhmodOGL)
		return false;

	// pull wgl functions
	for(int i=0; i<sizeof(kWGLFunctions)/sizeof(kWGLFunctions[0]); ++i) {
		void *p = (void *)GetProcAddress(mhmodOGL, kWGLFunctions[i]);

		if (!p) {
			Shutdown();
			return false;
		}

		((void **)static_cast<VDAPITableWGL *>(this))[i] = p;
	}

	return true;
}

void VDOpenGLBinding::Shutdown() {
	End();
	Detach();

	if (mhmodOGL) {
		FreeLibrary(mhmodOGL);
		mhmodOGL = NULL;
	}
}

bool VDOpenGLBinding::Attach(HDC hdc, int minColorBits, int minAlphaBits, int minDepthBits, int minStencilBits, bool doubleBuffer) {
	PIXELFORMATDESCRIPTOR pfd={};

	pfd.nSize			= sizeof(PIXELFORMATDESCRIPTOR);
	pfd.nVersion		= 1;
	pfd.dwFlags			= PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL;
	if (doubleBuffer)
		pfd.dwFlags |= PFD_DOUBLEBUFFER;
	pfd.iPixelType		= PFD_TYPE_RGBA;
	pfd.cColorBits		= minColorBits;
	pfd.cAlphaBits		= minAlphaBits;
	pfd.cDepthBits		= minDepthBits;
	pfd.cStencilBits	= minStencilBits;
	pfd.iLayerType		= PFD_MAIN_PLANE;

	int pf = ChoosePixelFormat(hdc, &pfd);
	if (!pf) {
		Detach();
		return false;
	}

	if (!SetPixelFormat(hdc, pf, &pfd)) {
		Detach();
		return false;
	}

	mhglrc = wglCreateContext(hdc);
	if (!mhglrc)
		return false;

	if (!Begin(hdc)) {
		Detach();
		return false;
	}

	for(int i=0; i<sizeof(kGLFunctions)/sizeof(kGLFunctions[0]); ++i) {
		void *p = (void *)GetProcAddress(mhmodOGL, kGLFunctions[i]);

		if (!p) {
			Detach();
			return false;
		}

		((void **)static_cast<VDAPITableOpenGL *>(this))[i] = p;
	}

	for(int i=0; i<sizeof(kGLExtFunctions)/sizeof(kGLExtFunctions[0]); ++i) {
		void *p = (void *)wglGetProcAddress(kGLExtFunctions[i]);

		((void **)static_cast<VDAPITableOpenGLEXT *>(this))[i] = p;
	}

	const char *ext = (const char *)glGetString(GL_EXTENSIONS);

	ARB_multitexture = false;
	NV_register_combiners = false;
	NV_register_combiners2 = false;
	NV_occlusion_query = false;
	ATI_fragment_shader = false;
	EXT_pixel_buffer_object = false;
	ARB_pixel_buffer_object = false;
	EXT_texture_env_combine = false;

	if (ext) {
		for(;;) {
			while(*ext == ' ')
				++ext;

			if (!*ext)
				break;

			const char *start = ext;
			while(*ext && *ext != ' ')
				++ext;

			int len = ext - start;

			switch(len) {
			case 19:
				if (!memcmp(start, "GL_ARB_multitexture", 19))
					ARB_multitexture = true;
				break;
			case 21:
				if (!memcmp(start, "GL_NV_occlusion_query", 21))
					NV_occlusion_query = true;
				break;

			case 22:
				if (!memcmp(start, "GL_ATI_fragment_shader", 22))
					ATI_fragment_shader = true;
				break;
			case 24:
				if (!memcmp(start, "GL_NV_register_combiners", 24))
					NV_register_combiners = true;
				break;
			case 25:
				if (!memcmp(start, "GL_NV_register_combiners2", 25))
					NV_register_combiners2 = true;
				else if (!memcmp(start, "GL_EXT_framebuffer_object", 25))
					EXT_framebuffer_object = true;
				break;
			case 26:
				if (!memcmp(start, "GL_EXT_pixel_buffer_object", 26))
					EXT_pixel_buffer_object = true;
				else if (!memcmp(start, "GL_ARB_pixel_buffer_object", 26))
					EXT_pixel_buffer_object = ARB_pixel_buffer_object = true;
				else if (!memcmp(start, "GL_EXT_texture_env_combine", 26))
					EXT_texture_env_combine = true;
				else if (!memcmp(start, "GL_ARB_texture_env_combine", 26))
					EXT_texture_env_combine = true;
				break;
			}
		}
	}

	ext = NULL;
	if (wglGetExtensionsStringARB)
		ext = wglGetExtensionsStringARB(hdc);
	else if (wglGetExtensionsStringEXT)
		ext = wglGetExtensionsStringEXT();

	EXT_swap_control = false;
	ARB_make_current_read = false;
	if (ext) {
		for(;;) {
			while(*ext == ' ')
				++ext;

			if (!*ext)
				break;

			const char *start = ext;
			while(*ext && *ext != ' ')
				++ext;

			int len = ext - start;

			switch(len) {
			case 20:
				if (!memcmp(start, "WGL_EXT_swap_control", 20))
					EXT_swap_control = true;
				break;
			case 25:
				if (!memcmp(start, "WGL_ARB_make_current_read", 25))
					ARB_make_current_read = true;
				break;
			}
		}
	}

	End();

	return true;
}

bool VDOpenGLBinding::AttachAux(HDC hdc, int minColorBits, int minAlphaBits, int minDepthBits, int minStencilBits, bool doubleBuffer) {
	PIXELFORMATDESCRIPTOR pfd={};

	pfd.nSize			= sizeof(PIXELFORMATDESCRIPTOR);
	pfd.nVersion		= 1;
	pfd.dwFlags			= PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL;
	if (doubleBuffer)
		pfd.dwFlags |= PFD_DOUBLEBUFFER;
	pfd.iPixelType		= PFD_TYPE_RGBA;
	pfd.cColorBits		= minColorBits;
	pfd.cAlphaBits		= minAlphaBits;
	pfd.cDepthBits		= minDepthBits;
	pfd.cStencilBits	= minStencilBits;
	pfd.iLayerType		= PFD_MAIN_PLANE;

	int pf = ChoosePixelFormat(hdc, &pfd);
	if (!pf)
		return false;

	if (!SetPixelFormat(hdc, pf, &pfd))
		return false;

	return true;
}

void VDOpenGLBinding::Detach() {
	if (mhglrc) {
		wglDeleteContext(mhglrc);
		mhglrc = NULL;
	}
}

bool VDOpenGLBinding::Begin(HDC hdc) {
	if (!wglMakeCurrent(hdc, mhglrc))
		return false;

	mhdc = hdc;
	return true;
}

void VDOpenGLBinding::End() {
	if (mhdc) {
		wglMakeCurrent(mhdc, NULL);
		mhdc = NULL;
	}
}

namespace {
	void CreateNVRegisterCombinerSetup(VDOpenGLBinding& gl, const VDOpenGLNVRegisterCombinerConfig& config, bool nvrc2) {
		// load base constants
		if (!nvrc2) {
			for(int i=0; i<config.mConstantCount; ++i)
				gl.glCombinerParameterfvNV(GL_CONSTANT_COLOR0_NV + i, config.mpConstants[i]);
		}

		static const GLenum kRegisterTable[16]={
			GL_ZERO,
			GL_DISCARD_NV,
			GL_SPARE0_NV,
			GL_SPARE1_NV,
			GL_PRIMARY_COLOR_NV,
			GL_SECONDARY_COLOR_NV,
			GL_CONSTANT_COLOR0_NV,
			GL_CONSTANT_COLOR1_NV,
			GL_SPARE0_PLUS_SECONDARY_COLOR_NV,
			GL_E_TIMES_F_NV,
			GL_ZERO,
			GL_ZERO,
			GL_TEXTURE0_ARB,
			GL_TEXTURE1_ARB,
			GL_TEXTURE2_ARB,
			GL_TEXTURE3_ARB,
		};

		static const GLenum kScaleTable[4]={
			GL_NONE,
			GL_SCALE_BY_TWO_NV,
			GL_SCALE_BY_FOUR_NV,
			GL_SCALE_BY_ONE_HALF_NV,
		};

		static const GLenum kSourceMappingTable[8]={
			GL_UNSIGNED_IDENTITY_NV,
			GL_UNSIGNED_INVERT_NV,
			GL_SIGNED_IDENTITY_NV,
			GL_SIGNED_NEGATE_NV,
			GL_EXPAND_NORMAL_NV,
			GL_EXPAND_NEGATE_NV,
			GL_HALF_BIAS_NORMAL_NV,
			GL_HALF_BIAS_NEGATE_NV
		};

		// create combiner stages
		const uint8 *src = config.mpByteCode;
		for(int stage=0; stage<config.mGeneralCombinerCount; ++stage) {
			GLenum stageToken = GL_COMBINER0_NV + stage;

			// load per-stage colors
			if (nvrc2) {
				uint8 c0 = src[0];
				uint8 c1 = src[1];
				src += 2;

				if (c0 != 0xff)
					gl.glCombinerStageParameterfvNV(GL_COMBINER0_NV + stage, GL_CONSTANT_COLOR0_NV, config.mpConstants[c0]);
				if (c1 != 0xff)
					gl.glCombinerStageParameterfvNV(GL_COMBINER0_NV + stage, GL_CONSTANT_COLOR1_NV, config.mpConstants[c1]);
				VDASSERT(!gl.glGetError());
			}

			// load combiner halves
			for(int half=0; half<2; ++half) {
				GLenum portionToken = half ? GL_ALPHA : GL_RGB;

				// decode output
				GLenum dst0 = kRegisterTable[src[0] >> 4];
				GLenum dst1 = kRegisterTable[src[1] & 15];
				GLenum dst2 = kRegisterTable[src[1] >> 4];
				GLenum dstscale = kScaleTable[src[0] & 3];
				GLenum dstbias = src[0] & 4 ? GL_BIAS_BY_NEGATIVE_ONE_HALF_NV : GL_NONE;
				GLenum dstDotAB = src[2] & 1 ? GL_TRUE : GL_FALSE;
				GLenum dstDotCD = src[2] & 2 ? GL_TRUE : GL_FALSE;
				GLenum dstMux = src[2] & 4 ? GL_TRUE : GL_FALSE;
				src += 3;

				gl.glCombinerOutputNV(stageToken, portionToken, dst0, dst1, dst2, dstscale, dstbias, dstDotAB, dstDotCD, dstMux);
				VDASSERT(!gl.glGetError());

				// decode sources
				for(int i=0; i<4; ++i) {
					uint8 c = *src++;
					GLenum srcreg = kRegisterTable[c & 15];
					GLenum srcmap = kSourceMappingTable[(c >> 4) & 7];
					GLenum srcportion = c & 0x80 ? GL_ALPHA : half ? GL_BLUE : GL_RGB;

					gl.glCombinerInputNV(stageToken, portionToken, GL_VARIABLE_A_NV + i, srcreg, srcmap, srcportion);
					VDASSERT(!gl.glGetError());
				}
			}
		}

		gl.glCombinerParameteriNV(GL_NUM_GENERAL_COMBINERS_NV, config.mGeneralCombinerCount);

		// load final combiner
		if (nvrc2) {
			uint8 c0 = src[0];
			uint8 c1 = src[1];
			src += 2;

			if (c0 != 0xff)
				gl.glCombinerParameterfvNV(GL_CONSTANT_COLOR0_NV, config.mpConstants[c0]);
			if (c1 != 0xff)
				gl.glCombinerParameterfvNV(GL_CONSTANT_COLOR1_NV, config.mpConstants[c1]);
			VDASSERT(!gl.glGetError());
		}

		for(int i=0; i<7; ++i) {
			uint8 c = *src++;
			GLenum srcreg = kRegisterTable[c & 15];
			GLenum srcmap = kSourceMappingTable[(c >> 4) & 7];
			GLenum srcportion = c & 0x80 ? GL_ALPHA : i == 6 ? GL_BLUE : GL_RGB;

			gl.glFinalCombinerInputNV(GL_VARIABLE_A_NV + i, srcreg, srcmap, srcportion);
			VDASSERT(!gl.glGetError());
		}
	}

	void CreateATIFragmentShader(VDOpenGLBinding& gl, const VDOpenGLATIFragmentShaderConfig& config) {
		enum {
			kTexAddrT0			= 0x00,
			kTexAddrT1			= 0x01,
			kTexAddrT2			= 0x02,
			kTexAddrT3			= 0x03,
			kTexAddrT4			= 0x04,
			kTexAddrT5			= 0x05,
			kTexAddrR0			= 0x08,
			kTexAddrR1			= 0x09,
			kTexAddrR2			= 0x0A,
			kTexAddrR3			= 0x0B,
			kTexAddrR4			= 0x0C,
			kTexAddrR5			= 0x0D,
			kTexSwizzleXYZ		= 0x00,
			kTexSwizzleXYW		= 0x10,
			kTexSwizzleXYZ_DZ	= 0x20,
			kTexSwizzleXYW_DW	= 0x30,
			kTexSwizzleMask		= 0x30,
			kTexModeTexcrd		= 0x40,
			kTexModeTexld		= 0x80,
			kTexModeMask		= 0xC0
		};

		enum {
			kChannelOpAdd,
			kChannelOpSub,
			kChannelOpMul,
			kChannelOpMad,
			kChannelOpLerp,
			kChannelOpMov,
			kChannelOpCnd,
			kChannelOpCnd0,
			kChannelOpDot2Add,
			kChannelOpDot3,
			kChannelOpDot4,
			kChannelOpTexcrd,
			kChannelOpTexld,
			kChannelOpMask			= 0x0F,
			kChannelOpModScaleMask	= 0x70,
			kChannelOpModScaleX2	= 0x10,
			kChannelOpModScaleX4	= 0x20,
			kChannelOpModScaleX8	= 0x30,
			kChannelOpModScaleD2	= 0x40,
			kChannelOpModScaleD4	= 0x50,
			kChannelOpModScaleD8	= 0x60,
			kChannelOpModSaturate	= 0x80
		};

		enum {
			kChanRegR0,
			kChanRegR1,
			kChanRegR2,
			kChanRegR3,
			kChanRegR4,
			kChanRegR5,
			kChanRegC0,
			kChanRegC1,
			kChanRegC2,
			kChanRegC3,
			kChanRegC4,
			kChanRegC5,
			kChanRegC6,
			kChanRegC7,
			kChanRegZero,
			kChanRegOne,
			kChanRegV0,
			kChanRegV1,
			kChanSrcMod2X		= 0x01,
			kChanSrcModComp		= 0x02,
			kChanSrcModNegate	= 0x04,
			kChanSrcModBias		= 0x08,
			kChanSrcSwizzleRed		= 0x10,
			kChanSrcSwizzleGreen	= 0x20,
			kChanSrcSwizzleBlue		= 0x30,
			kChanSrcSwizzleAlpha	= 0x40,
			kChanDstMaskRed		= 0x10,
			kChanDstMaskGreen	= 0x20,
			kChanDstMaskBlue	= 0x40,
			kChanDstMaskAlpha	= 0x80
		};

		VDASSERT(!gl.glGetError());
		gl.glBeginFragmentShaderATI();

		// load constants
		for(uint8 i=0; i<config.mConstantCount; ++i)
			gl.glSetFragmentShaderConstantATI(GL_CON_0_ATI + i, config.mpConstants[i]);
		VDASSERT(!gl.glGetError());

		// create passes
		const uint8 *src = config.mpByteCode;
		while(uint8 opCount = *src++) {
			// set up texture interpolators
			for(int i=0; i<6; ++i) {
				uint8 texOp = *src++;

				if (!texOp)
					continue;

				static const GLenum kTexSwizzleMode[4]={
					GL_SWIZZLE_STR_ATI,
					GL_SWIZZLE_STQ_ATI,
					GL_SWIZZLE_STR_DR_ATI,
					GL_SWIZZLE_STQ_DQ_ATI
				};

				GLenum dst = GL_REG_0_ATI + i;
				GLenum src = (texOp & 8 ? GL_REG_0_ATI : GL_TEXTURE0_ARB) + (texOp & 7);
				GLenum swizzle = kTexSwizzleMode[(texOp >> 4) & 3];

				switch(texOp & kTexModeMask) {
					case kTexModeTexcrd:
						gl.glPassTexCoordATI(dst, src, swizzle);
						break;
					case kTexModeTexld:
						gl.glSampleMapATI(dst, src, swizzle);
						break;
				}
			}
			VDASSERT(!gl.glGetError());

			// set up alu ops
			while(opCount--) {
				static const GLenum kOpTable[][2]={
					{ GL_ADD_ATI, 2 },
					{ GL_SUB_ATI, 2 },
					{ GL_MUL_ATI, 2 },
					{ GL_MAD_ATI, 3 },
					{ GL_LERP_ATI, 3 },
					{ GL_MOV_ATI, 1 },
					{ GL_CND_ATI, 3 },
					{ GL_CND0_ATI, 3 },
					{ GL_DOT2_ADD_ATI, 3 },
					{ GL_DOT3_ATI, 2 },
					{ GL_DOT4_ATI, 2 },
				};

				static const GLuint kDstModTable[16]={
					GL_NONE,
					GL_2X_BIT_ATI,
					GL_4X_BIT_ATI,
					GL_8X_BIT_ATI,
					GL_HALF_BIT_ATI,
					GL_QUARTER_BIT_ATI,
					GL_EIGHTH_BIT_ATI,
					GL_NONE,
					GL_SATURATE_BIT_ATI,
					GL_SATURATE_BIT_ATI | GL_2X_BIT_ATI,
					GL_SATURATE_BIT_ATI | GL_4X_BIT_ATI,
					GL_SATURATE_BIT_ATI | GL_8X_BIT_ATI,
					GL_SATURATE_BIT_ATI | GL_HALF_BIT_ATI,
					GL_SATURATE_BIT_ATI | GL_QUARTER_BIT_ATI,
					GL_SATURATE_BIT_ATI | GL_EIGHTH_BIT_ATI,
					GL_SATURATE_BIT_ATI,
				};

				static const GLenum kRegTable[]={
					GL_REG_0_ATI,
					GL_REG_1_ATI,
					GL_REG_2_ATI,
					GL_REG_3_ATI,
					GL_REG_4_ATI,
					GL_REG_5_ATI,
					GL_CON_0_ATI,
					GL_CON_1_ATI,
					GL_CON_2_ATI,
					GL_CON_3_ATI,
					GL_CON_4_ATI,
					GL_CON_5_ATI,
					GL_CON_6_ATI,
					GL_CON_7_ATI,
					GL_ZERO,
					GL_ONE,
					GL_PRIMARY_COLOR_ARB,
					GL_SECONDARY_INTERPOLATOR_ATI
				};

				static const GLenum kRepTable[]={
					GL_NONE,
					GL_RED,
					GL_GREEN,
					GL_BLUE,
					GL_ALPHA
				};

				const uint8 opcode = *src++;
				const uint8 dstarg = *src++;

				GLenum op = kOpTable[opcode & 15][0];
				GLenum dstMod = kDstModTable[opcode >> 4];
				GLenum dstReg = kRegTable[dstarg & 15];
				GLuint rgbMask = (dstarg >> 4) & 7;
				bool alphaMask = (dstarg & 0x80) != 0;

				int srcCount = kOpTable[opcode & 15][1];
				GLenum srcReg[3];
				GLenum srcRep[3];
				GLuint srcMod[3];
				for(int i=0; i<srcCount; ++i) {
					const uint8 mod = src[1];

					srcReg[i] = kRegTable[src[0]];
					srcRep[i] = kRepTable[mod >> 4];
					srcMod[i] = mod & 15;
					src += 2;
				}

				switch(srcCount) {
					case 1:
						if (rgbMask)
							gl.glColorFragmentOp1ATI(op, dstReg, rgbMask, dstMod, srcReg[0], srcRep[0], srcMod[0]);
						if (alphaMask)
							gl.glAlphaFragmentOp1ATI(op, dstReg, dstMod, srcReg[0], srcRep[0], srcMod[0]);
						break;
					case 2:
						if (rgbMask)
							gl.glColorFragmentOp2ATI(op, dstReg, rgbMask, dstMod, srcReg[0], srcRep[0], srcMod[0], srcReg[1], srcRep[1], srcMod[1]);
						if (alphaMask)
							gl.glAlphaFragmentOp2ATI(op, dstReg, dstMod, srcReg[0], srcRep[0], srcMod[0], srcReg[1], srcRep[1], srcMod[1]);
						break;
					case 3:
						if (rgbMask)
							gl.glColorFragmentOp3ATI(op, dstReg, rgbMask, dstMod, srcReg[0], srcRep[0], srcMod[0], srcReg[1], srcRep[1], srcMod[1], srcReg[2], srcRep[2], srcMod[2]);
						if (alphaMask)
							gl.glAlphaFragmentOp3ATI(op, dstReg, dstMod, srcReg[0], srcRep[0], srcMod[0], srcReg[1], srcRep[1], srcMod[1], srcReg[2], srcRep[2], srcMod[2]);
						break;
				}
				VDASSERT(!gl.glGetError());

			}
		}

		gl.glEndFragmentShaderATI();
		VDASSERT(!gl.glGetError());
	}
}

GLenum VDOpenGLBinding::InitTechniques(const VDOpenGLTechnique *techniques, int techniqueCount) {
	GLuint listBase = glGenLists(techniqueCount);

	for(int i=0; i<techniqueCount; ++i) {
		const VDOpenGLTechnique& tech = techniques[i];
		GLenum shader;

		switch(tech.mFragmentShaderMode) {
		case kVDOpenGLFragmentShaderModeNVRC2:
			if (!NV_register_combiners2)
				continue;
			// fall through
		case kVDOpenGLFragmentShaderModeNVRC:
			if (!NV_register_combiners)
				continue;
			break;
		case kVDOpenGLFragmentShaderModeATIFS:
			if (!ATI_fragment_shader)
				continue;
			break;
		}

		if (tech.mFragmentShaderMode == kVDOpenGLFragmentShaderModeATIFS) {
			shader = glGenFragmentShadersATI(1);
			glBindFragmentShaderATI(shader);
			CreateATIFragmentShader(*this, *(const VDOpenGLATIFragmentShaderConfig *)tech.mpFragmentShader);
			glBindFragmentShaderATI(0);
		}

		glNewList(listBase + i, GL_COMPILE);
		switch(tech.mFragmentShaderMode) {
		case kVDOpenGLFragmentShaderModeNVRC:
			glEnable(GL_REGISTER_COMBINERS_NV);
			glDisable(GL_PER_STAGE_CONSTANTS_NV);
			if (ATI_fragment_shader)
				glDisable(GL_FRAGMENT_SHADER_ATI);
			CreateNVRegisterCombinerSetup(*this, *(const VDOpenGLNVRegisterCombinerConfig *)tech.mpFragmentShader, false);
			break;
		case kVDOpenGLFragmentShaderModeNVRC2:
			glEnable(GL_REGISTER_COMBINERS_NV);
			glEnable(GL_PER_STAGE_CONSTANTS_NV);
			if (ATI_fragment_shader)
				glDisable(GL_FRAGMENT_SHADER_ATI);
			CreateNVRegisterCombinerSetup(*this, *(const VDOpenGLNVRegisterCombinerConfig *)tech.mpFragmentShader, true);
			break;
		case kVDOpenGLFragmentShaderModeATIFS:
			if (NV_register_combiners)
				glDisable(GL_REGISTER_COMBINERS_NV);
			glEnable(GL_FRAGMENT_SHADER_ATI);
			glBindFragmentShaderATI(shader);
			break;
		}
		glEndList();
	}

	return listBase;
}

void VDOpenGLBinding::DisableFragmentShaders() {
	if (NV_register_combiners)
		glDisable(GL_REGISTER_COMBINERS_NV);
	if (ATI_fragment_shader)
		glDisable(GL_FRAGMENT_SHADER_ATI);
}
